﻿//-------------------------------------------------------------------------------
// <copyright file="InMemoryContext.cs" company="bbv Software Services AG">
//   Copyright (c) 2009 bbv Software Services AG
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.
// </copyright>
//------------------------------------------------------------------------------
using System;
using System.Linq;
using System.Linq.Expressions;

namespace bbv.DomainDrivenDesign.Contexts
{
    using System.Collections.Generic;
    using Interfaces;
    using QueryableContexts;

    /// <summary>
    /// Represents an in memory context.
    /// </summary>
    public class InMemoryContext
    {
        internal readonly IDictionary<object, object> m_includes;

        private readonly IDictionary<object, object> m_idFuncs;
        /// <summary>
        /// A Dictionary that represents an in memory database.
        /// </summary>
        internal readonly IDictionary<object, object> m_dataSources;
        /// <summary>
        /// Initializes a new instance of the <see cref="InMemoryContext"/> class.
        /// </summary>
        internal InMemoryContext()
        {
            m_dataSources = new Dictionary<object, object>();
            m_idFuncs = new Dictionary<object, object>();
            m_includes = new Dictionary<object, object>();
        }

        internal void LoadJoinOption<T, TJoinWith, TKey>(Func<T, TKey> outerKeySelector, Func<TJoinWith, TKey> innerKeySelector, Func<T, TJoinWith, T> projection)
            where T : class
            where TJoinWith : class
        {
            var baseList = CreateQuery<T>();
            m_includes.Add(typeof(T).ToString() + typeof(TJoinWith), baseList.Join(CreateQuery<TJoinWith>().DefaultIfEmpty(), outerKeySelector, innerKeySelector, projection).Union(baseList));
        }
        internal void LoadGroupJoinOption<T, TJoinWith, TKey>(Func<T, TKey> outerKeySelector, Func<TJoinWith, TKey> innerKeySelector, Func<T, IEnumerable<TJoinWith>, T> resultSelector)
            where T : class
            where TJoinWith : class
        {
            var baseList = CreateQuery<T>();
            m_includes.Add(typeof(T).ToString() + typeof(TJoinWith), baseList.GroupJoin(CreateQuery<TJoinWith>().DefaultIfEmpty(), outerKeySelector, innerKeySelector, resultSelector).Union(baseList));
        }

        /// <summary>
        /// Creates a query of a particular type.
        /// </summary>
        /// <typeparam name="T">The desired type of the query.</typeparam>
        /// <returns>A new <see cref="T:bbv.DomainDrivenDesign.Interfaces.IQueryableContext`1"/>.</returns>
        internal IQueryableContext<T> CreateQuery<T>() where T : class
        {
            return GetInMemoryQueryableContext<T>();
        }

        /// <summary>
        /// Gets a table of a particular type.
        /// </summary>
        /// <typeparam name="T">The type of the table.</typeparam>
        /// <returns>The table as <see cref="T:bbv.DomainDrivenDesign.Interfaces.ITable`1"/></returns>
        internal ITable<T> GetTable<T>() where T : class
        {
            return GetInMemoryQueryableContext<T>();
        }

        /// <summary>
        /// Gets the in memory queryable context.
        /// </summary>
        /// <typeparam name="T">The type of the desired queryable context.</typeparam>
        /// <returns>An <see cref="T:bbv.DomainDrivenDesign.QueryableContexts.InMemoryQueryableContext`1"/>.</returns>
        private InMemoryQueryableContext<T> GetInMemoryQueryableContext<T>() where T : class
        {
            if (!this.m_dataSources.ContainsKey(typeof(T)))
            {
                this.m_dataSources.Add(typeof(T), new InMemoryQueryableContext<T>(this));
            }

            var foo = this.m_dataSources[typeof(T)] as InMemoryQueryableContext<T>;
            m_dataSources[typeof(T)] = foo;
            return foo;
        }
        internal void SetIdFunc<T, TProperty>(Func<T, TProperty, bool> func) where T : class
        {
            if (!m_idFuncs.ContainsKey(typeof(T)))
            {
                m_idFuncs.Add(typeof(T), func);
            }
            else
            {
                m_idFuncs[typeof(T)] = func;
            }
        }
        internal T GetById<T, TPropertyType>(TPropertyType id) where T : class
        {
            var func = m_idFuncs[typeof(T)] as Func<T, TPropertyType, bool>;
            return GetInMemoryQueryableContext<T>().SingleOrDefault(ent => func(ent, id));
        }
    }
}
